/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package jate.core;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Iterator;

/**
 *
 * @author milan
 */
public class Yate {

    protected static boolean useSocket = false;
    protected static Socket yateSocket = null;
    protected static PrintStream writer = null;
    protected static BufferedReader reader = null;
    public static boolean debugOn = false;

    public static boolean init() {
        Yate.useSocket = false;
        Yate.reader = new BufferedReader(new InputStreamReader(System.in));
        Yate.writer = System.out;
        return true;
    }

    public static boolean init(String host, int port) {
        try {
            Yate.yateSocket = new Socket(host, port);
            Yate.writer = new PrintStream(Yate.yateSocket.getOutputStream(), true);
            Yate.reader = new BufferedReader(new InputStreamReader(Yate.yateSocket.getInputStream()));
            Yate.useSocket = true;
        }
        catch (UnknownHostException e) {
            System.err.println("Don't know about host: " + host + ", port: " + port);
            System.exit(1);
        }
        catch(IOException e){
            System.err.println("Couldn't get I/O for the connection to: " + host + ", port: " + port);
            System.exit(1);
        }

        return true;
    }

    public static void initRole(String role){
        Yate.print(role);
    }

    public static void output(String str) {
        if (Yate.useSocket) {
            Yate.print("%%>output:" + str);
        } else {
            System.err.println(str);
        }
    }

    public static void debug(String str) {
        if (Yate.debugOn) {
            Yate.output(str);
        }
    }

    protected static void print(String str) {
        Yate.writer.println(str);
    }

    public static void dispatch(YateMessage message) throws Exception {
        Yate.debug("Dispatching: " + message);
        if (message.type.compareTo("outgoing") != 0) {
            throw new Exception("Attempt to dispatch message type: " + message.type);
        }
        String id = Yate.escape(message.id);
        long ts = 0 + message.origin;
        String name = Yate.escape(message.name);
        String retVal = Yate.escape(message.retVal);

        String rawCommand = "%%>message:" + id + ":" + ts + ":" + name + ":" + retVal + Yate.encodeParameters(message);
        Yate.debug("command: " + rawCommand);
        Yate.print(rawCommand);
        message.type = "dispatched";
    }

    public static void acknowledge(YateMessage message) throws Exception {
        if (message.type.compareTo("incoming") != 0) {
            throw new Exception("Attempt to acknowledge message type: " + message.type);
        }
        String id = Yate.escape(message.id);
        String handled = Yate.boolToStr(message.handled);
        String name = Yate.escape(message.name);
        String retVal = Yate.escape(message.retVal);
        Yate.print("%%<message:" + id + ":" + handled + ":" + name + ":" + retVal + Yate.encodeParameters(message));
        message.type = "acknowledged";
    }

    public static YateMessage getMessage() throws IOException {
        YateMessage message = null;
        String line = "";

        // read input
        line = Yate.reader.readLine();
        if (line == null) {
            Yate.debug("Line is null");
            return null;
        } else if (line.equals("")) {
            message = new YateMessage("empty");
            return message;
        }

        Yate.debug("received: " + line);

        String[] messageParts = line.split(":");

        if (messageParts[0].equals("%%>message")) {
            message = Yate.checkPartsCount(messageParts, 5);
            if(message == null){
                message = new YateMessage(Yate.unescape(messageParts[3]), Yate.unescape(messageParts[4]), Yate.unescape(messageParts[1]));
                message.type = "incoming";
                message.origin = Integer.parseInt(messageParts[2]);
                Yate.fillParameters(message, messageParts, 5);
            }
        } else if (messageParts[0].equals("%%<message")) {
            message = Yate.checkPartsCount(messageParts, 4);
            if(message == null){
                String retVal = "";
                if(messageParts.length >= 5){
                    retVal = messageParts[4];
                }
                message = new YateMessage(Yate.unescape(messageParts[3]), Yate.unescape(retVal), Yate.unescape(messageParts[1]));
                message.type = "answer";
                message.handled = Yate.strToBool(messageParts[2]);
                Yate.fillParameters(message, messageParts, 5);
            }
        } else if (messageParts[0].equals("%%<install")) {
            message = Yate.checkPartsCount(messageParts, 4);
            if(message == null){
                message = new YateMessage(Yate.unescape(messageParts[2]), "", Yate.unescape(messageParts[1]));
                message.type = "installed";
                message.handled = Yate.strToBool(messageParts[3]);
            }
        } else if (messageParts[0].equals("%%<uninstall")) {
            message = new YateMessage(Yate.unescape(messageParts[2]), "", Yate.unescape(messageParts[1]));
            if(message == null){
                message.type = "uninstalled";
                message.handled = Yate.strToBool(messageParts[3]);
            }
        } else if (messageParts[0].equals("%%<watch")) {
            message = Yate.checkPartsCount(messageParts, 3);
            if(message == null){
                message = new YateMessage(Yate.unescape(messageParts[1]));
                message.type = "watched";
                message.handled = Yate.strToBool(messageParts[2]);
            }
        } else if (messageParts[0].equals("%%<unwatch")) {
            message = Yate.checkPartsCount(messageParts, 3);
            if(message == null){
                message = new YateMessage(Yate.unescape(messageParts[1]));
                message.type = "unwatched";
                message.handled = Yate.strToBool(messageParts[2]);
            }
        } else if (messageParts[0].equals("%%<connect")) {
            message = Yate.checkPartsCount(messageParts, 3);
            if(message == null){
                message = new YateMessage(Yate.unescape(messageParts[1]));
                message.type = "connected";
                message.handled = Yate.strToBool(messageParts[2]);
            }
        } else if (messageParts[0].equals("%%<setlocal")) {
            message = Yate.checkPartsCount(messageParts, 4);
            if(message == null){
                message = new YateMessage(Yate.unescape(messageParts[1]), Yate.unescape(messageParts[2]));
                message.type = "setlocal";
                message.handled = Yate.strToBool(messageParts[3]);
            }
        }

        Yate.debug(">> MSG: " + message);
        return message;
    }

    protected static YateMessage checkPartsCount(String[] messageParts, int expectedCount){
        YateMessage message = null;
        if(messageParts.length < expectedCount){
            message = new YateMessage("empty");
            Yate.output("Invalid number of parts. Expected " + expectedCount + ", but " + messageParts.length + " found: ");
            for(String s: messageParts){
                Yate.output("  " + s);
            }
            return message;
        }
        else{            
            return null;
        }
    }

    protected static void fillParameters(YateMessage message, String[] messageParts, int offset) {
        int n = messageParts.length;
        for (int i = offset; i < n; i++) {
            String s = messageParts[i];
            int q = s.indexOf("=");
            if (q == -1) {
                message.params.put(Yate.unescape(s), null);
            } else {
                message.params.put(Yate.unescape(s.substring(0, q)), Yate.unescape(s.substring(q + 1)));
            }
        }
    }

    protected static boolean strToBool(String str) {
        return (str.equals("true")) ? true : false;
    }

    protected static String boolToStr(boolean bool) {
        return (bool) ? "true" : "false";
    }

    protected static String escape(String str, char extra) {
        str += "";
        String result = new String("");
        for (char c : str.toCharArray()) {
            if (((int) c < 32) || (c == ':') || (extra != ' ' && c == extra)) {
                c = (char) (c + 64);
                result += '%';
            } else if (c == '%') {
                result += c;
            }
            result += c;
        }
        return result;
    }

    protected static String escape(String str) {
        return Yate.escape(str, ' ');
    }

    protected static String unescape(String str) {
        String result = new String("");
        boolean nextEscaped = false;
        for (char c : str.toCharArray()) {
            if (nextEscaped) {
                if (c != '%') {
                    result += (char) (c - 64);
                } else {
                    result += c;
                }
                nextEscaped = false;
            } else {
                if (c != '%') {
                    result += c;
                } else {
                    nextEscaped = true;
                }
            }
        }
        return result;
    }

    protected static String encodeParameters(YateMessage message) {
        String result = "";
        String key = "";
        Iterator iterator = message.params.keySet().iterator();
        while (iterator.hasNext()) {
            key = (String) iterator.next();
            result += ":" + Yate.escape(key, '=') + "=" + Yate.escape(message.params.get(key));
        }
        return result;
    }

    public static void install(String name, int priority, String filtname, String filtvalue) {
        name = Yate.escape(name);
        if (!filtname.equals("")) {
            filtname = ":" + filtname + ":" + filtvalue;
        }
        Yate.print("%%>install:" + priority + ":" + name + filtname);
    }

    public static void install(String name, int priority) {
        Yate.install(name, priority, "", "");
    }

    public static void install(String name) {
        Yate.install(name, 100);
    }

    public static void uninstall(String name) {
        name = Yate.escape(name);
        Yate.print("%%>uninstall:" + name);
    }

    public static void watch(String name) {
        name = Yate.escape(name);
        Yate.print("%%>watch:" + name);
    }

    public static void unwatch(String name) {
        name = Yate.escape(name);
        Yate.print("%%>unwatch:" + name);
    }

    public static void setLocal(String name, String value) {
        name = Yate.escape(name);
        value = Yate.escape(value);
        Yate.print("%%>setlocal:" + name + ":" + value);
    }
}
